package com.game.grid;

import java.util.*;
import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View.OnTouchListener;
import com.game.grid.Location;

public class game extends View implements OnTouchListener {
	
	//timer used to fire update and gameloop routines.
	public Timer gameTimer;
	
	//Andriod paint objects that are used to color things in-game
	Paint paint1 = new Paint();
	Paint paint2 = new Paint();
	
	//box sizes constants
	public static final int LARGEST_BOX = 96;
	public static final int LARGE_BOX = 85;
	public static final int SMALL_BOX = 75;
	public static final int SMALLEST_BOX = 64;
	private int curBoxSize;
	
	//array sizes 
	public static final int LARGEST_BOX_ARRAY = 3;
	public static final int LARGE_BOX_ARRAY = 3;
	public static final int SMALL_BOX_ARRAY = 4;
	public static final int SMALLEST_BOX_ARRAY = 5;
	
	//player Starting Location  is scaled by the starting size of the screen
	private int startingX;
	private int startingY;
	
	//screen size when game is initialized.
	private int screenSizeX;
	private int screenSizeY;
	
	//starting speed for boxes falling
	public static final int START_SPEED = 45; //in fames per box
	
	//containers
	private LinkedList<Box[]> onScreen;  //list of all boxes[] onscreen
	private LinkedList<Box[]> offScreen; //list of all boxes[] offscreen
	private LinkedList<Box> collisionList; //list of all boxes that player can collide with.
	private ArrayList<Location> playerLocations; //
	
	//player location
	private Location playerPos;  //the current location of the player
	private Box startBox; //start box variable, used more than once
	private int playerBox; //the box that the player is in at any given moment.
	
	private long frameCounter;  //counts the number of frames
	private int updateCounter;  //used to debug by counting number of updates.
	
	//Game Time variables
	private int seconds;  //seconds that have elapsed
	private int minutes;  //Mins that have elapsed
	
	private int speedMult = 1;
	
	//boolean state flags.
	private boolean boxesChanged;
	private boolean gameStarted = false;
	public boolean gamePaused = false;
	public boolean drawing = false;
	private boolean touched = false;
	
	//------------functions---------------
	public game(Context context) { //constructor.
		super(context);
		// TODO Auto-generated constructor stub
		
		//setting up the screen so that its touchable and has event listener.
		setFocusable(true);
		setFocusableInTouchMode(true);
		this.setOnTouchListener(this);
		
		//Setting up colors
		paint1.setColor(Color.WHITE);
		paint2.setColor(Color.RED);
        
		//initializing the lists.
    	onScreen = new LinkedList<Box[]>();
    	offScreen = new LinkedList<Box[]>();
    	collisionList = new LinkedList<Box>();
    	
    	//initializing player position and box.
    	playerPos = new Location(startingX, startingY);
    	playerBox = 0;
    	
    	curBoxSize = LARGEST_BOX; //the game starts with the boxes being the largest
    	//size.
    	
    	boxesChanged = false;  //flag used for determining if the boxes have already
    	//been changed by the program so that we don't have change the size of the boxes
    	//multiple times.
    	
    	gameTimer = new Timer(); //inializing game timer.
	}
	
	/*
	 * Function for creating the boxes at the start of the game
	 * ----Creates the arrays for 6 straight up to allow the player
	 * ----to not have to immediately move.
	 */
	public void createBoxes() {
		
		Box[] temp = new Box[5];	
		temp[0] = startBox;
		temp[0].setUpBox();
		collisionList.add(temp[0]);
		onScreen.add(temp);
		
		
		Box row1 = new Box(startBox.getLeftSide(), startBox.getTopSide()-curBoxSize, curBoxSize);
		temp = new Box[5];
		temp[0] = row1;
		temp[0].setUpBox();
		collisionList.add(temp[0]);
		onScreen.add(temp);
		
		
		Box row2 = new Box(startBox.getLeftSide(), startBox.getTopSide()-curBoxSize*2, curBoxSize);
		temp = new Box[5];
		temp[0] = row2;
		temp[0].setUpBox();
		collisionList.add(temp[0]);
		onScreen.add(temp);
		
		
		Box row3 = new Box(startBox.getLeftSide(), startBox.getTopSide()-curBoxSize*3, curBoxSize);
		temp = new Box[5];
		temp[0] = row3;
		temp[0].setUpBox();
		collisionList.add(temp[0]);
		onScreen.add(temp);
		
		
		Box row4 = new Box(startBox.getLeftSide(), startBox.getTopSide()-curBoxSize*4, curBoxSize);
		temp = new Box[5];
		temp[0] = row4;
		temp[0].setUpBox();
		collisionList.add(temp[0]);
		onScreen.add(temp);
		
		
		Box row5 = new Box(startBox.getLeftSide(), startBox.getTopSide()-curBoxSize*5, curBoxSize);
		temp = new Box[5];
		temp[0] = row5;
		temp[0].setUpBox();
		collisionList.add(temp[0]);
		onScreen.add(temp);
		
		//all boxes onscreen for start are created
		
		Box row6 = new Box(startBox.getLeftSide(), 0, curBoxSize);
		temp = new Box[5];
		temp[0] = row6;
		temp[0].setUpBox();
		offScreen.add(temp);
		
		//creating randomly generated offscreen boxes
		for(int i = 0; i < 9;i++) {
			createOffScreenBoxes();
		}
	
		/*
		 * Setting up game timers to run the gameLoop and update functions
		 * respectively.
		 */
    	gameTimer.schedule(new TimerTask() { public void run() { gameLoop();}}, 33, 33);
    	gameTimer.schedule(new TimerTask() { public void run() { update();}}, 1, 1);
	}
	
	/*
	 * This is the main logic function where all of the checks are called.
	 * Is fired every 33 milliseconds.
	 */
	private void gameLoop() {
		if(!gamePaused) { //if game is paused then it never does anything.
			if(!drawing){ //drawing boolean flag ensures that concurrent modification doesn't occur
				checkSize();
				playerCollision();
				checkBoxesOnScreen();
				moveBoxes();
				postInvalidate();
			}
			/*
			 * Below is the logic for the circles that follow the player
			 * the touced boolean checks that the player is still touching the phone
			 * if the player isn't then we remove locations until only 1 is left, the current
			 * location of the player.
			 */
			if(touched) {
				touched = false;
			} else {
				if(playerLocations.size() != 1) {
					playerLocations.remove(0);
					playerLocations.trimToSize();
				} 
			}
		}
	}
	
	/*
	 * Checks to see if the lowest box is still viewable
	 * If it isn't then it removes it 
	 * Also checks if a box needs to be added from the offscreen array to
	 * the onscreen array.
	 */
	private void checkBoxesOnScreen() {
		Box[] testBox = onScreen.getFirst(); //local test array.
		
		//checking if the first box in the onscreen array is offscreen.
		if(testBox[0].getTopSide() > screenSizeY+10) {
			for(Box b : onScreen.getFirst()) {
				if(b!=null) {
					playerBox--;
					collisionList.removeFirst();
				}
			}
			onScreen.removeFirst();  
		}
		
		testBox = onScreen.getLast();
		
		//checking to see if the last box onscreen is fully onscreen
		if(testBox[0].getTopSide() >= -10) {
			onScreen.add(offScreen.removeFirst());
			createOffScreenBoxes();
			for(Box x:onScreen.getLast()) {
				if(x!=null) {
					x.setY(testBox[0].getTopSide() - x.getBoxSize());
				}
			}
			for(Box b:onScreen.getLast()) {
				if(b!=null) {
					collisionList.add(b);  //anytime a box gets added on screen it must be added to this list.
				}
			}
		}
	}
	
	//Function to move all onscreen boxes by a set speed.
	private void moveBoxes() {
    	for(Box[] t:onScreen) {
    		for(Box x:t) {
    			if(x!=null) {
    				x.moveUp(speedMult);
    			}
    		}
    	}
	}
	
	/*
	 * (non-Javadoc)
	 * @see android.view.View#onSizeChanged(int, int, int, int)
	 * 
	 * Function is called by the andriod system when the screen is initally set up
	 * Use this function mainly to set variables that record the screen size
	 * and use that information to adjust/scale other variables.
	 */
	@Override
	public void onSizeChanged(int w, int h, int oldw, int oldh) { 
		startingX = w/2;
		startingY = h - (curBoxSize/2);
		screenSizeX = w;
		screenSizeY = h;
		playerPos.setX(startingX);
		playerPos.setY(startingY);
		
		startBox = new Box((w/2)-(curBoxSize/2),h-curBoxSize,curBoxSize);
		createBoxes();
		
		//also use this opportunity to initialize some variables/zero the clock
		frameCounter = 0;
		seconds = 0;
		minutes = 0;
		
		//adding the first player location to the list of player locations.
		playerLocations = new ArrayList<Location>();
		playerLocations.add(new Location(playerPos.getX(), playerPos.getY()));
	}
	
	/*
	 * (non-Javadoc)
	 * @see android.view.View#onDraw(android.graphics.Canvas)
	 * 
	 * The draw function for andriod.
	 * we don't really have much control over when this is called, thus the
	 * drawing boolean flag.
	 */
    @Override
    public void onDraw(Canvas canvas) {
    	drawing = true;
    	
    	//canvas.drawText("Num objects in array " + onScreen.size(), startingX, 20, paint1);
    	//canvas.drawRect(testBox.getLeftSide(),testBox.getTopSide(),testBox.getRightSide(), testBox.getBottomSide(), paint1); //test code
    	//LinkedList<Box[]> onScreenTemp = (LinkedList<Box[]>) onScreen.clone();
    	for(Box[] t:onScreen) {
    		for(Box x:t) {
    			if(x!=null) {
    				canvas.drawRect(x.getLeftSide(),x.getTopSide(),x.getRightSide(), x.getBottomSide(), paint1);
    				canvas.drawLine(x.getLeftSide(), x.getBottomSide()-x.getBoxSize()/2, x.getRightSide(), x.getBottomSide()-x.getBoxSize()/2, paint2);
    				if(x.upBox) {
    					canvas.drawLine(x.getLeftSide()+x.getBoxSize()/2, x.getBottomSide(), x.getLeftSide()+x.getBoxSize()/2, x.getTopSide(), paint2);
    				}
    			}
    		}
    	}
    	//Loop to draw players current and previous locations.
    	for(Location loc:playerLocations) {
    		canvas.drawCircle(loc.getX(), loc.getY(), 5, paint2);
    	}
    	
    	//This writes the time in the top left corner.
    	if(seconds >= 10)
    		canvas.drawText("Time : " + minutes + ":" + seconds, 10, 60, paint2);
    	else
    		canvas.drawText("Time : " + minutes + ":0" + seconds, 10, 60, paint2);
    	
    	drawing = false; //drawing ended
    }

    /*
     * (non-Javadoc)
     * @see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent)
     * 
     * Called when the layer is touching the screen, provides an event object and view
     */
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// TODO Auto-generated method stub
		
		touched = true; //the player has touched the screen.
		
		//we only move the player if the person touches the screen within 50 pixels of the
		//player's location.
		double temp = Math.hypot(event.getX() - playerPos.getX(),event.getY() - playerPos.getY());
		if(temp < 50) {
			playerPos.setX((int)event.getX());
			playerPos.setY((int)event.getY());
			
			//adds the player's new location to the list of player locations.
			if(playerLocations.size() == 5) {
				playerLocations.remove(0);
				playerLocations.trimToSize();
				playerLocations.add(new Location(playerPos.getX(), playerPos.getY()));
			} else {
				playerLocations.add(new Location(playerPos.getX(), playerPos.getY()));
			}
		}
				
		return true;
	}
	/*
	 * Player collision code...
	 * Currently fail options use system.exit which resets the program.
	 */
	private void playerCollision() { 
		if(playerBox != 0 && playerBox < collisionList.size()-1) {
			if(playerPos.getX() >= collisionList.get(playerBox+1).getLeftSide() && playerPos.getX() <= collisionList.get(playerBox+1).getRightSide() 
					&& playerPos.getY() >= collisionList.get(playerBox+1).getTopSide() && playerPos.getY() <= collisionList.get(playerBox+1).getBottomSide()) {
				playerBox++;
			} else if(playerPos.getX() >= collisionList.get(playerBox-1).getLeftSide() && playerPos.getX() <= collisionList.get(playerBox-1).getRightSide() 
					&& playerPos.getY() >= collisionList.get(playerBox-1).getTopSide() && playerPos.getY() <= collisionList.get(playerBox-1).getBottomSide()) {
				playerBox--;
			} else if(playerPos.getX() >= collisionList.get(playerBox).getLeftSide() && playerPos.getX() <= collisionList.get(playerBox).getRightSide() 
					&& playerPos.getY() >= collisionList.get(playerBox).getTopSide() && playerPos.getY() <= collisionList.get(playerBox).getBottomSide()) {}
			else {System.exit(0);}
		}else if(playerBox == collisionList.size()-1) {
			if(playerPos.getX() >= collisionList.get(playerBox-1).getLeftSide() && playerPos.getX() <= collisionList.get(playerBox-1).getRightSide() 
					&& playerPos.getY() >= collisionList.get(playerBox-1).getTopSide() && playerPos.getY() <= collisionList.get(playerBox-1).getBottomSide()) {
				playerBox--;
			} else if(playerPos.getX() >= collisionList.get(playerBox).getLeftSide() && playerPos.getX() <= collisionList.get(playerBox).getRightSide() 
					&& playerPos.getY() >= collisionList.get(playerBox).getTopSide() && playerPos.getY() <= collisionList.get(playerBox).getBottomSide()) {}
			else {System.exit(0);}
		}else {
			if(playerPos.getX() >= collisionList.get(playerBox+1).getLeftSide() && playerPos.getX() <= collisionList.get(playerBox+1).getRightSide() 
					&& playerPos.getY() >= collisionList.get(playerBox+1).getTopSide() && playerPos.getY() <= collisionList.get(playerBox+1).getBottomSide()) {
				playerBox++;
			}else if(playerPos.getY() >= screenSizeY-5) {System.exit(0);} 
				else if(playerPos.getX() >= collisionList.get(playerBox).getLeftSide() && playerPos.getX() <= collisionList.get(playerBox).getRightSide() 
					&& playerPos.getY() >= collisionList.get(playerBox).getTopSide() && playerPos.getY() <= collisionList.get(playerBox).getBottomSide()) {
				
			} else {System.exit(0);}
		}
	}
	
	/*
	 * the update function, is used to calculate the time elapsed.
	 */
	public void update() {
		if(!gamePaused) {
			frameCounter++;
			
			if(frameCounter == 1000) {
				seconds++;
				frameCounter = 0;
			}
			
			if(seconds == 60) {
				minutes++;
				seconds = 0;
				speedMult++;
			}
		}
	}
		
	/*
	 * creates 1 offscreen box row.
	 * function creates the random maze feature.
	 */
	private void createOffScreenBoxes() {
		Box[] test = offScreen.getLast();
		int x, y, rightside;
		x = screenSizeX/2;
		y = 0;
		rightside = 0;
		for(Box b: test) {
			if(b != null) {
				if(b.upBox) {
					x = b.getLeftSide();
					rightside = b.getRightSide();
				}
			}
		}
		
		Box startBox = new Box(x,y,curBoxSize);
		Box[] temp = offScreen.getLast(); //used as a referance for location and size
		
		//centers the starting box if the box size has changed.
		if(temp[0].getBoxSize() > startBox.getBoxSize()) {
			startBox.addToX((temp[0].getBoxSize() - startBox.getBoxSize())/2);
		} else {
			if(startBox.getRightSide()/2 > screenSizeX/2) {
				startBox.addToX((rightside - startBox.getRightSide())/2);
			}
		}
		
		//initalizes local variables
		int location = 0;
		Box[] row = new Box[5];
		row[location] = startBox;
		boolean done = false;
		double rand;
		int direction = 0; //1=right 2=left
		
		//logic for the random rows is below.
		while(!done) {
			rand = Math.random() * 10;
			if(direction == 0) {
				if(rand > 7) {
					row[location].setUpBox();
					done = true;
				} else if(rand <= 3) {
					//go right
					if(row[location].getRightSide() + startBox.getBoxSize() < screenSizeX) {
						location++;
						row[location] = new Box(row[location-1].getLeftSide()+startBox.getBoxSize(),0, startBox.getBoxSize());
						direction = 1;
					} else {
						//go up
						done = true;
						row[location].setUpBox();
					}
					
				} else {
					//go left
					if(row[location].getLeftSide() - row[location].getBoxSize() > 0) {
						location++;
						row[location] = new Box(row[location-1].getLeftSide()-startBox.getBoxSize(),0, startBox.getBoxSize());
						direction = 2;
					} else {
						//go up
						done = true;
						row[location].setUpBox();
					}					
				}
			}
			else if(direction == 1) {
				if(rand > 7) {
					if(row[location].getRightSide() + startBox.getBoxSize() < screenSizeX) {
						location++;
						row[location] = new Box(row[location-1].getLeftSide()+startBox.getBoxSize(),0, startBox.getBoxSize());
						//direction = 1;
					} else {
						//go up
						done = true;
						row[location].setUpBox();
					}
				} else {
					done = true;
					row[location].setUpBox();
				}
			}
			else {
				if(rand > 7) {
					if(row[location].getLeftSide() - startBox.getBoxSize() > 0) {
						location++;
						row[location] = new Box(row[location-1].getLeftSide()-startBox.getBoxSize(),0, startBox.getBoxSize());
						//direction = 2;
					} else {
						//go up
						done = true;
						row[location].setUpBox();
					}
				} else {
					done = true;
					row[location].setUpBox();
				}
			}
		}
		offScreen.add(row); //adds row to array.
	}
	
	/*
	 * Checks to see if the box size should have changed and then changes it.
	 * Currently based on time.
	 */
	private void checkSize() {
		
		if((seconds % 15) == 0 && !boxesChanged) {
			if(curBoxSize == SMALLEST_BOX) {
				curBoxSize = LARGEST_BOX;
			} else if(curBoxSize == SMALL_BOX) {
				curBoxSize = SMALLEST_BOX;
			} else if(curBoxSize == LARGE_BOX) {
				curBoxSize = SMALL_BOX;
			} else if(curBoxSize == LARGEST_BOX) {
				curBoxSize = LARGE_BOX;
			}
			boxesChanged = true;
		}
		
		if((seconds % 15) != 0 && boxesChanged) {
			boxesChanged = false;
		}
	}
}
